fix(web): preserve unsent user-input answers across thread switches - #4592
fix(web): preserve unsent user-input answers across thread switches#4592ipanasenko wants to merge 3 commits into
Conversation
Draft answers to a provider's user-input request lived in ChatView state, keyed by requestId. The router creates a new match per thread, so switching threads unmounted ChatView and discarded a typed but unsent answer. Move the drafts into a persisted zustand store keyed by requestId, so they outlive the component (and a reload). The draft is cleared once the answer is submitted successfully; failures keep it so the user can retry.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
`record[key] = value` invokes the prototype setter for a `"__proto__"` question id instead of creating an own property, so the draft vanished on serialize. Build the next answers map with a computed key in an object literal (and rest-strip on removal), both of which define own properties.
ApprovabilityVerdict: Needs human review This PR introduces a new persisted state store with non-trivial cleanup logic. There is an unresolved high-severity comment identifying a potential bug where thread-switching may incorrectly clear drafts from other threads. You can customize Macroscope's approvability policy. Learn more. |
Two follow-ups from review: - Clearing the draft on submit success blanked the panel during the window before the `user-input.resolved` activity arrives, since the request is still listed as pending until then. Clear when a request drops out of the pending list instead, which also covers cancelled and stale-failed requests. - Answers and the question index were evicted independently, so the retention cap could keep one half of a draft without the other. Store both in a single per-request record so eviction is atomic.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 753fff0. Configure here.
| } | ||
| } | ||
| seenPendingUserInputRequestIdsRef.current = currentRequestIds; | ||
| }, [pendingUserInputs]); |
There was a problem hiding this comment.
Thread switch clears other drafts
High Severity
The pending-input cleanup effect compares the previous pendingUserInputs snapshot to the current thread’s list, but seenPendingUserInputRequestIdsRef is not reset when routeThreadKey / threadId changes. Navigating to another thread (same mounted ChatView) treats the prior thread’s pending requestIds as removed and calls clearPendingUserInputDraft, wiping persisted drafts while those requests are still open on the original thread.
Reviewed by Cursor Bugbot for commit 753fff0. Configure here.


Problem
A typed-but-unsent answer to a provider's user-input question is lost as soon as you switch to another thread and come back.
Screen.Recording.2026-07-26.at.11.30.20.PM.mov
Cause
The draft answers lived in
ChatViewcomponent state —pendingUserInputAnswersByRequestId/pendingUserInputQuestionIndexByRequestId. The router creates a new match per$threadId, so a thread switch unmountsChatViewand throws that state away. Nothing was persisted.Change
apps/web/src/pendingUserInputDraftStore.ts: a zustand store persisted tolocalStorage(t3code:pending-user-input-drafts:v1, same pattern asterminalUiStateStore) holding draft answers and the active question index keyed byrequestId. Has a 50-request retention cap so abandoned drafts don't accumulate.ChatViewreads/writes that store instead of localuseState. Answer logic (togglePendingUserInputOptionSelection,setPendingUserInputCustomAnswer) is unchanged — the store just takes an updater.On returning to the thread,
ChatComposer's existing hydration effect restores the editor text fromactivePendingProgress.customAnswer, so typed text, selected options, and multi-question position all come back. Drafts now also survive a reload.Testing
tsgo --noEmitonapps/web— cleanvp linton the touched files — cleanvp test run --project unit— 8 new store tests plus existingpendingUserInput,ChatView.logic,AppRootsuites (52 tests) passAlso verified manually live in the app against a real pending question.
Note
Preserve unsent user-input draft answers across thread switches in ChatView
ChatViewContentwith a persisted zustand store (pendingUserInputDraftStore.ts) that retains draft answers and the active question index per request id.derivePendingUserInputs.__proto__) via object spread and JSON serialization, and evicts the oldest drafts beyond a cap of 50 retained request ids.Macroscope summarized 753fff0.
Note
Low Risk
Scoped to client-side draft UX in localStorage with bounded retention; no auth, API, or server behavior changes.
Overview
Unsent answers to provider user-input prompts now survive thread navigation and page reloads by moving draft state out of
ChatViewinto a persisted Zustand store keyed byrequestId.ChatViewno longer keepspendingUserInputAnswersByRequestId/pendingUserInputQuestionIndexByRequestIdin component state (which was dropped on thread unmount). It reads and updatespendingUserInputDraftStore, which persists tolocalStorage(t3code:pending-user-input-drafts:v1) and retains up to 50 request drafts with oldest-first eviction. Draft cleanup runs when arequestIdfalls off the pending user-input list (not immediately on submit), so the panel does not flash empty while the request is still pending untiluser-input.resolvedarrives.The new store reuses existing answer helpers via updaters, normalizes question index, and builds answer maps safely for edge keys like
__proto__. Eight unit tests cover isolation, retention, and clearing behavior.Reviewed by Cursor Bugbot for commit 753fff0. Bugbot is set up for automated code reviews on this repo. Configure here.